/**
* AutoSWF - Cody Cushing. March 26, 2009
* Copyright (c) 2009 Cody Cushing
* No use restrictions. Maintain this notice in all versions. 
**/

package com.autoswf.automator
{
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	
	/**
	 * Stores or loads serialized events captured from an AutomationObserver, allowing assisted 
	 * test case generation or replay of execution of functional test cases. Not intended for 
	 * production, use in a console app.
	 */
	public class AutomationClient extends EventDispatcher implements IAutomationClient
	{
		protected var timer:Timer = new Timer(100, 1);
		protected var playing:Boolean;
		
		
		/**
		 * Constructor. 
		 */
		public function AutomationClient()
		{
			timer.addEventListener(TimerEvent.TIMER_COMPLETE, timer_delayHandler);
		}
		
		
		protected var _recording:Boolean;
		public function get recording():Boolean
		{
			return _recording;
		}
		public function set recording(value:Boolean):void
		{
			_recording = value;
		}


		protected var _testCase:XML = <root/>		
		/**
		 * Allows a preexisting automation script to be loaded and executed. Stops current replay 
		 * if in progress. Returns the current automation script, whether loaded or produced by 
		 * notifications from the AutomationObserver.
		 */
		public function get testCase():XML
		{
			return _testCase;
		}
		public function set testCase(value:XML):void
		{
			_testCase = value;
			playing = false;
			timer.reset();
		}
		
		
		protected var _eventTypes:Array;
		/**
		 * An array of strings corresponding to event types to be recorded. If Array is null 
		 * all events will be recorded. 
		 */
		public function get eventTypes():Array
		{
			return _eventTypes;
		}
		public function set eventTypes(value:Array):void
		{
			_eventTypes = value;
		}
		
		
		protected var _currentDescriptor:XML;
		
		/** Returns the active descriptor. */
		public function get currentDescriptor():XML
		{
			return _currentDescriptor;
		}
		
		
		protected var _minDelay:int = 1;
		
		/** Minimum delay requiered before sending another descriptor. */
		public function set minDelay(value:int):void
		{
			_minDelay = value;
		}
		
		
		protected var _maxDelay:int = -1;
		
		/** Minimum delay allowed before sending the next descriptor. */
		public function set maxDelay(value:int):void
		{
			_maxDelay = value;
		}
		
		
		protected var _remoteLoadCommand:IRemoteLoadCommand;
		/**
		 * [IAutomationClient method]
		 * Command provided by AutomationObserver to execute for running a descriptor.
		 */		
		public function set remoteLoadCommand(value:IRemoteLoadCommand):void
		{
			_remoteLoadCommand = value;
		}

		
		/**
		 * [IAutomationClient method]
		 * Called by AutomationObserver to save a new descriptor.
		 */
		public function notify(descriptor:String):void
		{
			var node:XML = XML(descriptor);
			if(playing || !_recording || _eventTypes && _eventTypes.indexOf(node.@type) != -1)
			{
				return
			}
			_testCase.appendChild(node);
			dispatchEvent(new AutomationEvent(AutomationEvent.DESCRIPTOR_ADDED));
		}
		
		
		/**
		 * Clear the current test case and reset state. 
		 */		
		public function reset():void
		{
			_testCase = <root/>
			_recording = false;
			playing = false;
		}

		
		/**
		 * Starts timer to cycle through script, loading descriptors via command and 
		 * pausing until the next. 
		 */
		public function runTestCase():void
		{
			if(!_testCase.node.length())
			{
				return
			}
			
			_recording = false;
			playing = true;
			dispatchEvent(new AutomationEvent(AutomationEvent.SCRIPT_STARTED));
			
			timer.delay = clampDelay(_testCase.node[0].@delay);
			timer.start();
		}
		
		
		/**
		 * Ends the current timeout delay and executes the next command. 
		 */		
		public function skipDelay():void
		{
			loadNext();
		}
		
		
		protected function timer_delayHandler(event:TimerEvent):void
		{
			loadNext();
			dispatchEvent(new AutomationEvent(AutomationEvent.DESCRIPTOR_REMOVED));
		}
		
		
		/**
		 * Execute the next descriptor through the remoteLoadCommand.
		 */		
		protected function loadNext():void
		{
			if(_testCase.node.length() == 0)
			{
				return;
			}
			
			timer.reset();
			
			_currentDescriptor = _testCase.node[0];
			delete _testCase.node[0];
			
			/* Send descriptor to AutomationObserver for deserialization and execution. */
			_remoteLoadCommand.execute(_currentDescriptor.toXMLString());
			
			if(_testCase.node.length())  /* If there is another descriptor, run it */
			{
				timer.delay = clampDelay(_testCase.node[0].@delay);
				timer.start();
			}
			else /* There are no more descriptors, update state and notify of completion. */
			{
				playing = false;
				dispatchEvent(new AutomationEvent(AutomationEvent.SCRIPT_FINISHED));
			}
		}
		
		
		/**
		 * Returns the minimum or maximum delay allowed.
		 */
		protected function clampDelay(delay:uint):uint
		{
			delay = Math.max(_minDelay, delay);
			return (_maxDelay == -1) ? delay : Math.min(_maxDelay, delay);
		}
	}
}


